The TADS Alternate Library
Version 2.0

Sense Passing


Copyright 2000 by Kevin Forchione.
This is part of the TADS Alternate Library Authors Manual.

Introduction and Table of Contents




Sense Passing

 

Alt owes much of its sense-passing concepts, and is greatly indebted, to the pioneering efforts of the TADS 3 development team, notably the concepts of Materials, Connectors, and Senses developed by Michael J. Roberts, as well as work done in TADS 2 library extensions such as Sense.t and Scope.t.

 

The Scope() Method

 

Algorithms concerned with working out a path along a containment tree will tend to have common characteristics, however Alt takes a different tack from ADV3, Sense.t, or WorldClass. The main driver behind the library’s sense passing mechanism is the scope() method. This method takes 4 arguments: sense, path, target, and callbackObj.

 

sense is one of the Sense class objects defined by Alt. These objects define the sense attribute to be evaluated by the method. Alt defines four senses: sight, sound, smell, and touch. Tasting is an extension of touching.  Passing nil creates a path that traverses the tree without regard to sense.

 

path is passed around the methods used to traverse the tree and contains a valid sense path from the vantage to all objects sensible by the vantage or to the target object, if target isn’t nil.

 

target indicates an object that we are evaluating for a particular sense accessibility by the vantage. If target is nil then the path will contain all objects accessible via the indicated sense for the vantage.

 

callbackObj is an object of the Callback class that can be passed to control traversal across the tree, and is especially useful for performing checks along the path. Alt defines one callbackObj: lightingCallback, which indicates whether we have light along the path.

 

The scope() method returns a list that is the path constructed between the vantage (initial object) and all objects sensible to the vantage by a given sense, or a path between the vantage and the target object.

 

The scopeLocations() and scopeContents() Methods

 

These methods are called by scope() and used by it to traverse the object tree. They should never be called directly. scopeLocations() first determines if the object can sense its locations (in the case of Connector class objects there may be more than one location. Other classes return only one location). The scopeConents method performs a similar function for the object’s contents.

 

The senseFromAbove() and senseFromBelow Methods

 

These methods should also never be called directly by author code. They are used by the scopeLocations() and scopeContents() methods when an object cannot sense its locations or contents and the scope() method has been passed a target. In this situation these methods “feel” ahead for the target, traversing the tree, from the point at which the sense was blocked, without regard to any sense in order to determine if the target lies further along the path. If the target is found using these methods then the scopeLocations() / scopeContents() methods set the appropriate attributes on the target:

 

       target.obstructedSense     indicates the sense that failed to detect the target

       target.obstructor          indicates the first object in the path that blocked accessibility to the target.

 

These attributes are then retrieved by the DeepVerb.cantReach() method and control is directed to the appropriate object (the target’s obstructor) and the appropriate cantSense method (i.e. if the obstructedSense is sight then the target’s cantSee() method is called).

 

The canSenseObj() Method

 

This method is used to determine if a vantage can sense an object using a particular sense. The method takes the following arguments:

 

          sense           The Sense class object corresponding to the sense being used to detect the target

          target           The object that is being sensed by the vantage.

 

The method returns true if the target can be sensed by the vantage using the specified sense; otherwise it returns nil.

 

This method is called specifically by the DeepVerb.validXoSense() methods, which are called from the checkValidity() function during preCommand(). At the preCommand() stage we are only interested in whether the object can be sensed by the sense appropriate for the verb.

 

If the object’s senseByPreferred attribute has been set to true by the DeepVerb.validXo() methods then checkValidity() bypasses executing DeepVerb.validXoSense(), saving time in the validation step.

 

The canSensePreferred() Method

 

This method is used to determine if the target can be sensed by the vantage using any sense. The method takes two arguments:

 

prefSense     Indicates the preferred sense used to detect the target. The method will first attempt to detect the target using this sense. If the target cannot be detected using the preferred sense then the method continues sensing using the other senses available to the vantage.

          target          The object that is being sensed by the vantage.

 

The method returns true if the target can be sensed by the vantage using any sense at all; otherwise it returns nil. Additionally the target’s senseByPreferred is set to true if the object has been sensed by the preferred sense; otherwise this attribute is set to nil.

 

This method is used specifically by the DeepVerb.validXo() methods

 

The canSenseLocations() and canSenseContents() Methods

 

These methods are used by scope() to determine if the object allows sensing across the boundary between its contents and its locations. The method takes two arguments:

 

          sense           The Sense class object corresponding to the sense being used to detect the target

          isVantage      A Boolean indicating whether this object is the vantage (i.e. the first object in the sensing chain.)

 

These methods can be modified by the author to control how an object behaves with regard to sensing. The methods should return true to indicate that sensing is allowed; otherwise they should return nil.

 

The cantSense Methods

 

Alt defines a cantSense method for each of the senses. Thus cantSee(), cantHear(), cantSmell(), and cantTouch() each correspond to the four senses defined by Alt. When accessibility fails, either during the parsing or preCommand’s checkAccessibility() stages then control is passed to the obstructor’s corresponding cantSense method. The methods first determine whether the actor can see the object. If the object is visible, but not accessible to the sense required by the verb a message appropriate to the action is displayed: “You can’t reach that from here.” If the object is not visible then the “You don’t see any foo.” message is displayed.

 

The isLit() Method

 

This method is used to detemine whether an object is lit or not. Unlike ADV.T, isLit() is always used as a method for calculating the presence of light for the object. If an object provides light, such as a Room or a Lightsource, then the object’s lightsOn attribute is true; if the object does not provide light on its own then lightsOn should be set to nil. The determination of whether or not the object is lit is handled by the isLit Method. This method takes one argument:

 

          vantage       The “observer” of the object for which light is being determined.

 

Unlike ADV.T, whether or not an object is lit is dependent upon the relationship of the object and an observer, the vantage. It might be the case that the observer is inside the object, such as a steel safe. In this case the safe might be lit from the inside by a candle, while on the outside lighting is dependent upon other sources in the room. Is the safe lit? This is not such a simple question.

 

It depends not only upon whether the candle inside is burning, or whether the room has a Lightsource, but requires a perspective when asking the question, “is the safe lit?” We must necessarily ask, “for whom?”

 

The Senses

 

Alt defines 4 senses: sight, smell, sound, and touch. Taste is considered an extension of touch. Senses are objects of the Sense class. Each sense has a thruProp attribute that is set to an attribute pointer corresponding to that particular sense. The attribute is used by the Materials class to determine whether the particular material allows passage of the sense.

 

Materials

 

The Material class is one of the most elegant aspects of Alt’s sense passing mechanism and owes its conception to Michael J. Roberts. Alt shamelessly borrows from this conception as a simplification of the scheme developed by Sense.t that should make life much easier for authors.

 

Alt defines the following Materials (based on the schema developed by Michael J. Roberts)

 

Material

seeThru

hearThru

smellThru

touchThru

altima

nil

nil

nil

nil

glass

true

nil

nil

nil

paper

nil

true

true

nil

fineMesh

true

true

true

nil

coarseMesh

true

true

true

true

 

 

altima is the default Material of every game object inheriting from Thing class and does not allow passage of sight, sound, smell, or touch.

 

glass allows passage of sight, but not of sound, smell, or touch.

 

paper allows passage of sound and smell, but not sight or touch.

 

fineMesh allows passage of sight, sound, and smell, but not touch.

 

coarseMesh allows passage of sight, sound, smell, and touch.

 

Connectors

 

Connectors act as a conduit for sense-passing between locations. This concept isn’t new, WorldClass employed a similar strategy, however Alt’s implementation is based upon the work of Michael Roberts. Connectors derive from Alt’s MultiLoc class. This means that they are Floating class objects that are moved to their locations prior to the parsing and execution of each command.

 

Unlike other classes, however, the Connector class returns the entire list of its locations when the scope methods request getLocations(). This entire list is used in creating the object tree, and in evaluating object accessibility.

 

Connector class objects also inherit and evaluate their own connectorMaterial attribute, which is used to determine if the sense can pass through the Connector’s material.

 

 Accesibility Failure

 

The final ingredient of Alt’s sense passing mechanism involves the parser hooks for DeepVerb and preCommand(). When a command is issued by the player the parser calls the DeepVerb parser hooks validIoList(), validIo(), validDoList() and validDo() to help it decide in the disambiguation of the indirect and direct objects. Alt’s DeepVerb validXoList() methods return nil by default, meaning that every object found by the parser using its dictionary match is to be passed to the DeepVerb validXo() methods for further validation.

 

The validXo() methods send a message to the actor.canSensePreferred() method, passing the corresponding DeepVerb.xoSense and the target object to be sensed. The canSensePreferred() method will return either true or nil depending upon whether the object can be sensed by the actor by any sense. It will also set the target object’s sensedByPreferred attribute to either true or nil if the object is sensible using the DeepVerb.xoSense.

 

If the object cannot be sensed at all by the actor then the parser will display “I don’t see any foo here”.

 

If the object is at least sensible to one sense by the actor then disambiguation and object resolution continues until control passes to preCommand(). At preCommand() the command is re-validated, this time checking the indirect and direct objects’ sensedByPreferred attribute. If the attribute is true then the command is considered valid and processing continues through command execution.

 

If sensedByPreferred is nil then a message is sent to the corresponding verb.validXoSense(). This method uses actor.cansSenseObj() to determine if the object can be sensed by the verb.xoSense. If the method returns true then processing continues through command execution.

 

If DeepVerb.validXoSense() fails then control is passed to DeepVerb.cantReach(). This method retrieves the target.obstructor and target.obstructedSense values and passes control to the corresponding obstructor’s cantSense method. Finally we reset the target objects sensedByPreferred attribute and abort the command.

 

The advantage of validating accessibility in this way is that by the time we reach the cantSense methods for the obstructor all of the elements of command have been determined and we are able to redirect control to other portions of the program without having to parse player command phrases.

 

An Example

 

Suppose we want to connect two rooms so that an actor can smell bread cooking in the kitchen while standing in the pantry. The bread is in the stove, which is a closed OpenableContainer.

 

#include <stdif.h>

 

modify story

    startingLocation = kitchen

    title = "Alt Advanced Starter Game"

    headline = "An Interactive Fiction"

    introduction = "Welcome to TADS Alternate Library..."

;

 

/* starting location */

kitchen: Room

    sDesc = "Kitchen"

    lDesc = "This is the Kitchen."

;

 

stove: OpenableContainer

    location = kitchen

    noun = 'stove' 'oven'

    isOpen = nil

    sDesc = "stove"

;

 

loaf: FoodItem

    location = stove

    noun = 'loaf'

    adjective = 'bread'

    sDesc = "loaf of bread"

    smellDesc = "Smells delicious!"

;

 

After creating the story file we need to set the #include paths for compiling. These can be set from the Workbench’s Build àSettings menu by selecting the Build Settings Include tab and adding the following directories:

 

          alt

          alt\classes

          alt\functions

          alt\grammar

          alt\objects

 

We can now compile the source. The game should display:

 

Welcome to TADS Alternate Library...

 

Alt Advanced Starter Game

An Interactive Fiction

Release 1 / Serial number 000718

Alt Library Version 2.0.0

 

Kitchen

       This is the Kitchen.

       You see a stove here.

 

 

If we attempt to smell the bread we get the following message:

 

>smell loaf

You can't see any loaf here.

 

This is because the stove has been defined as closed. Closed OpenableContainers force the sense to pass through their materials. Open OpenableContainers do not impede sense passage regardless of their material. Since the stove doesn’t define a material attribute it inherits it from Thing, which means that its default material is altima. Altima doesn’t allow any senses to pass through an object, so the bread cannot be detected.

 

Because smell has failed the library then tries to detect the loaf using another sense. In this case the actor fails to sense any loaf and so the library displays a default message that is sight-oriented: “You don’t see any loaf here.” rather than smell-oriented.

 

If we open the stove all senses are able to pass through it, and thus we can now sense the bread:

 

>open stove

Opening the stove reveals a loaf of bread.

 

>smell loaf

Smells delicious!

 

In order to allow the actor to smell the bread while the stove is closed we must change the material of the stove. Paper is the ideal material for allowing smell and sound to pass through the object, while preventing sight and touch.

 

stove: OpenableContainer

    location = kitchen

    noun = 'stove' 'oven'

    isOpen = nil

    sDesc = "stove"

   

    /*

     *  Paper allows sound and smell to pass

     */

    material = paper

;

 

Now when we smell the loaf we get a smell description whether the stove is open or closed. But suppose we want the smell to display when the stove is open, whether the actor is smelling or not? Normally a daemon would be activated, but with Alt object reactions we can handle this from scopeEndCommand().

 

loaf: FoodItem

    location = stove

    noun = 'loaf'

    adjective = 'bread'

    sDesc = "loaf of bread"

    smellDesc = "The smell of baked bread permeates the room."

    scopeEndCommand = {

        if (!(gVerb() == smell && gDobj() == self))

            if (gActor().canSenseObj(sight, self))

                "\b<<self.smellDesc>>";

    };

 

First scopeEndCommand() checks to see if we’re attempting to smell the bread. Since we will have already displayed the smelldesc if we <<smell loaf>> there is no need to display the message again. Also, since we only want the message to display when the oven door is open we check to see if the actor can see the loaf. canSenseObj(sight, self) will return true if the actor can see the loaf; otherwise it returns nil.

 

Technically our message will display even when the actor is holding the loaf, which could be a little annoying. How can scopeEndCommand() be modified to display only when the actor can see the loaf, but it isn’t being carried by an actor?

 

The next step is to add our pantry definition.

 

kitchen: Room

    sDesc = "Kitchen"

    lDesc = "This is the Kitchen."

    north = pantry

;

 

pantry: Room

    sDesc = "Pantry"

    lDesc = "This is the pantry."

    south = kitchen

;

 

Both the kitchen and pantry are top-level locations, which means that they aren’t connected to one another along the object tree. The object tree consists of a network of objects linked by locations and contents. The kitchen is linked to the pantry by travel properties north and south. Given that objects only pass senses along the object tree, how are we to pass senses across these two rooms?

 

Alt’s Connector class is used as a conduit for senses between locations. Because the Connector is a MultiLoc object we can do this in a variety of ways. Suppose we connect the two rooms by a Passage that is also a Connector.

 

kitchen: Room

    sDesc = "Kitchen"

    lDesc = "This is the Kitchen. An archway to the north leads to the

        pantry."

    north = archway

;

 

pantry: Room

    sDesc = "Pantry"

    lDesc = "This is the pantry. An archway to the south leads to the

        kitchen."

    south = archway

;

 

archway: Connector, Passage

    foundIn = [kitchen, pantry]

    sDesc = "archway"

    noun = 'archway' 'arch'

    connectorMaterial = paper

;

 

Now our kitchen and pantry are connected by the archway. If you open the oven door and walk into the pantry you’ll notice that the loaf.smelldesc doesn’t display. This is because the loaf is no longer visible to the actor.

 

Notice that the Connector class is listed first in the archway definition and that the connectorMaterial attribute is defined. Unlike normal game objects Connectors must define a connectorMaterial, the default being altima, which doesn’t allow any senses to pass through the object. If you find that your senses are not being passed across locations joined by a Connector check that it assigns the appropriate Material to its connectorMaterial attribute.

 

 How can we modify scopEndCommand() so that the message is displayed the first time the oven door is opened and each time the actor enters the kitchen, but is not displayed on consecutive turns within the room?